package utils

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
)

type millisec int

func (m *millisec) Duration() time.Duration {
	return time.Duration(*m) * time.Millisecond
}

func sleepMs(ms int) {
	m := millisec(ms)
	time.Sleep(m.Duration())
}

func okTaskFunc() error { return nil }
func getSleepTaskFunc(ms int) TaskFunc {
	return func() error {
		sleepMs(ms)
		return nil
	}
}
func getErrorTaskFunc(e error) TaskFunc {
	return func() error { return e }
}
func getSleepErrorTaskFunc(ms int, err error) TaskFunc {
	return func() error {
		sleepMs(ms)
		return err
	}
}

type TaskStatus int

//go:generate stringer -type=TaskStatus -linecomment
const (
	TS_READY TaskStatus = iota
	TS_RUNNING
	TS_SUCCEED
	TS_FAILED
	TS_TIMEOUT
	TS_STOPPED
)

func (s TaskStatus) Finished() bool {
	switch s {
	case TS_READY, TS_RUNNING:
		return false
	default:
		return true
	}
}
func (s TaskStatus) IsRunning() bool {
	return s == TS_RUNNING
}

var (
	ErrActionNil       = errors.New("action func is nil")
	ErrDetectNil       = errors.New("detect func is nil")
	ErrActionTimeout   = errors.New("action func timeout")
	ErrDetectTimeout   = errors.New("detect func timeout")
	ErrTaskTimeout     = errors.New("task timeout")
	ErrTaskStopped     = errors.New("task stopped")
	ErrTaskNil         = errors.New("task is nil")
	ErrTaskRunning     = errors.New("task is running")
	ErrTaskFinished    = errors.New("task is finished")
	ErrChildTaskFailed = errors.New("child task failed")
)

type TaskFunc func() error

type ITask interface {
	GetName() string
	GetTimeout() time.Duration
	Run() error
	IsRunning() bool
	Stop()
	Succeed() bool
	Error() error
	Status() TaskStatus
	Report(bool) string
}

func NewSyncTask(name string, ms millisec, f TaskFunc) ITask {
	t := &syncTask{
		Name:       name,
		Timeout:    ms.Duration(),
		ActionFunc: f,
		errChan:    make(chan error),
		stopChan:   make(chan struct{}),
	}
	return t
}

func NewAsyncTask(name string, timeoutMs millisec, action, detect TaskFunc, detectIntervalMs millisec, detectBefore bool) ITask {
	t := &asyncTask{
		DetectFunc:         detect,
		DetectBeforeAction: detectBefore,
		DetectInterval:     detectIntervalMs.Duration(),
	}
	t.Name, t.Timeout, t.ActionFunc = name, timeoutMs.Duration(), action
	t.errChan = make(chan error)
	return t
}

type syncTask struct {
	// 名称
	Name string
	// 动作函数
	ActionFunc TaskFunc
	// 任务超时时长
	Timeout time.Duration
	// 失败信息
	err error
	// 状态
	status TaskStatus
	// 结果通道
	errChan chan error
	// 停止信号通道
	stopChan chan struct{}
	// 同步锁
	lock sync.Mutex
}

func (s *syncTask) GetName() string {
	return s.Name
}

func (s *syncTask) GetTimeout() time.Duration {
	return s.Timeout
}

func (s *syncTask) Run() error {
	var e error
	e = s.begin()
	if e != nil {
		return e
	}
	if s.ActionFunc == nil {
		s.end(ErrActionNil)
		return ErrActionNil
	}
	t := &TimeLimitedExecutor{
		Task:     s.ActionFunc,
		Duration: s.Timeout,
	}
	go func() {
		e := t.Run()
		s.errChan <- e
	}()
	e = <-s.errChan
	s.end(e)
	return e
}
func (s *syncTask) IsRunning() bool {
	return s.status.IsRunning()
}
func (s *syncTask) Stop() {
	if s.status == TS_RUNNING {
		s.errChan <- ErrTaskStopped
		<-s.stopChan
	}
}

func (s *syncTask) Succeed() bool {
	return s.Status() == TS_SUCCEED
}

func (s *syncTask) Error() error {
	return s.err
}

func (s *syncTask) Status() TaskStatus {
	return s.status
}

func (s *syncTask) Report(print bool) string {
	str := fmt.Sprintf("task name: %s, timeout: %v, succeed: %v, status: %s, error: %v\n", s.Name, s.Timeout, s.Succeed(), s.status, s.err)
	if print {
		logrus.Info(str)
	}
	return str
}
func (s *syncTask) begin() error {
	s.lock.Lock()
	if s.IsRunning() {
		s.lock.Unlock()
		// s.end(ErrTaskRunning)
		return ErrTaskRunning
	}
	defer s.lock.Unlock()
	s.status = TS_RUNNING
	s.err = nil
	logrus.Infof("task %s started", s.Name)
	return nil
}
func (s *syncTask) end(e error) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.err = e
	switch e {
	case ErrActionTimeout, ErrDetectTimeout, ErrTaskTimeout:
		s.status = TS_TIMEOUT
	case ErrTaskStopped:
		s.status = TS_STOPPED
	case nil:
		s.status = TS_SUCCEED
	default:
		s.status = TS_FAILED
	}
	b := s.Succeed()
	if b {
		logrus.Infof("task %s succeed", s.Name)
	} else {
		logrus.Errorf("task %s failed, error: %v", s.Name, s.err)
	}
	if e == ErrTaskStopped {
		s.stopChan <- struct{}{}
	}
}

type asyncTask struct {
	syncTask
	DetectFunc         TaskFunc
	DetectBeforeAction bool
	DetectInterval     time.Duration
}

func (t *asyncTask) Run() error {
	var err error
	err = t.begin()
	if err != nil {
		return err
	}

	if t.ActionFunc == nil {
		t.end(ErrActionNil)
		return ErrActionNil
	}
	if t.DetectFunc == nil {
		t.end(ErrDetectNil)
		return ErrDetectNil
	}
	t1 := NewTimeLimitedTask(t.ActionFunc, t.Timeout)
	t2 := NewTimeLimitedTask(t.DetectFunc, t.DetectInterval)
	if t.DetectBeforeAction {
		err = t2.Run()
		if err == nil {
			// first detection ok
			t.end(nil)
			return nil
		}
	}
	// action func execution
	err = t1.Run()

	// if the action func failed, the whole task failed
	if err != nil {
		t.end(err)
		return err
	}

	// remained time for detect func
	timeoutTimer := time.AfterFunc(t.Timeout-t1.LastExecDuration, func() {
		t.errChan <- ErrTaskTimeout
	})
	defer timeoutTimer.Stop()

	// repeat exec detect func
	repeatCh := time.Tick(t.DetectInterval)
	go func() {
		for range repeatCh {
			e := t2.Run()
			if e == nil {
				// detect ok
				t.errChan <- nil
				return
			} else if e == ErrActionTimeout {
				// detect timeout, the whole task failed
				t.errChan <- ErrDetectTimeout
				return
			}
			// detect failed not process
		}

	}()
	e := <-t.errChan
	t.end(e)
	return e
}
